\section{【实现】实模式到保护模式的切换}\label{ux5b9eux73b0ux5b9eux6a21ux5f0fux5230ux4fddux62a4ux6a21ux5f0fux7684ux5207ux6362}

BIOS把bootloader从硬盘（即是我们刚才生成的ucore.img）的第一个扇区（即是我们刚才生成的bootblock）读出来并拷贝到内存一个特定的地址0x7c00处，然后BIOS会跳转到那个地址（（即CS=0，EIP=0x7c00））继续执行。至此BIOS的初始化工作做完了，进一步的工作交给了ucore的bootloader。

bootloader从哪里开始执行呢？我们【实验2-2
编译运行bootloader】中描述make工作过程的第五步就是生成了一个bootblock.asm，它的前面几行是：

\begin{lstlisting}
    obj/bootblock.o:     file format elf32-i386
    Disassembly of section .text:
    00007c00 <start>:
    .set CR0_PE_ON,      0x1         # protected mode enable flag
    .globl start
    start:
      .code16                     # Assemble for 16-bit mode
      cli                         # Disable interrupts
      7c00: fa                    cli
\end{lstlisting}

上述代码片段指出了bootblock（即bootloader）在0x7c00虚拟地址（在这里虚拟地址=线性地址=物理地址）处的指令为``cli''，如果读者再回头看看bootasm.S中的12\textasciitilde{}15行：

\begin{lstlisting}
    .globl start
    start:
      .code16                     # Assemble for 16-bit mode
      cli                         # Disable interrupts
      cld                         # String operations increment
\end{lstlisting}

就可以发现二者是完全一致的。而这个虚拟地址的设定是通过链接器ld完成的，我们【实验2-2
编译运行bootloader】中描述make工作过程的第四步： i386-elf-ld -N -e start
-Ttext 0x7C00 -o obj/bootblock.o obj/bootasm.o obj/bootmain.o

其中``-e start''指出了bootblock的入口地址为start，而``-Ttext
0x7C00''指出了代码段的起始地址为0x7c00，这也就导致start位置的虚拟地址为0x7c00。

从0x7c00开始，bootloader用了21条汇编指令完成了初始化和切换到保护模式的工作。其具体步骤如下：

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  关中断，并清除方向标志，即将DF置``0''，这样(E)SI及(E)DI的修改为增量。
  cli \# Disable interrupts cld \# String operations increment
\item
  清零各数据段寄存器：DS、ES、FS xorw \%ax,\%ax \# Segment number zero
  movw \%ax,\%ds \# -\textgreater{} Data Segment movw \%ax,\%es \#
  -\textgreater{} Extra Segment movw \%ax,\%ss \# -\textgreater{} Stack
  Segment
\item
  使能A20地址线，这样80386就可以突破1MB访存现在，而可访问4GB的32位地址空间了。可回顾2.2.1节的【历史：A20地址线与处理器向下兼容】。
  seta20.1: inb \$0x64,\%al \# Wait for not busy testb \$0x2,\%al jnz
  seta20.1 movb \$0xd1,\%al \# 0xd1 -\textgreater{} port 0x64 outb
  \%al,\$0x64 seta20.2: inb \$0x64,\%al \# Wait for not busy testb
  \$0x2,\%al jnz seta20.2 movb \$0xdf,\%al \# 0xdf -\textgreater{} port
  0x60 outb \%al,\$0x60
\item
  建立全局描述符表（可回顾2.2.3节对全局描述符表的介绍），使能80386的保护模式（可回顾2.2.4节对CR0寄存器的介绍）。lgdt指令把gdt表的起始地址和界限（gdt的大小-1）装入GDTR寄存器中。而指令``movl
  \%eax，\%cr0''把保护模式开启位置为1，这时已经做好进入80386保护模式的准备，但还没有进入80386保护模式
  lgdt gdtdesc movl \%cr0, \%eax orl \$CR0\_PE\_ON, \%eax movl \%eax,
  \%cr0

  gdtdesc指出了全局描述符表（可以看成是段描述符组成的一个数组）的起始位置在gdt符号处，而gdt符号处放置了三个段描述符的信息
  gdt: SEG\_NULLASM \# null seg SEG\_ASM(STA\_X\textbar{}STA\_R, 0x0,
  0xffffffff) \# code seg SEG\_ASM(STA\_W, 0x0, 0xffffffff) \# data seg
  每个段描述符占8个字节，第一个是NULL段描述符，没有意义，表示全局描述符表的开始，紧接着是代码段描述符（位于全局描述符表的0x8处的位置），具有可读（STA\_R）和可执行（STA\_X）的属性，并且段起始地址为0，段大小为4GB；接下来是数据段描述符（位于全局描述符表的0x10处的位置），具有可读（STA\_R）和可写（STA\_W）的属性，并且段起始地址为0，段大小为4GB。
\item
  通过长跳转指令进入保护模式。80386在执行长跳转指令时，会重新加载\(PROT_MODE_CSEG的值（即0x8）到CS中，同时把\)protcseg的值赋给EIP，这样80386就会把CS的值作为全局描述符表的索引来找到对应的代码段描述符，设定当前的EIP为0x7c32(即protcseg标号所在的段内偏移)，
  根据2.2.3节描述的分段机制中虚拟地址到线性地址转换转换的基本过程，可以知道线性地址（即物理地址）为：
  gdt{[}CS{]}.base\_addr+EIP=0x0+0x7c32=0x7c32 ljmp \$PROT\_MODE\_CSEG,
  \$protcseg
\item
  执行完上面的这条汇编语句后，bootloader让80386从实模式进入了保护模式。由于在访问数据或栈时需要用DS/ES/FS/GS和SS段寄存器作为全局描述符表的下标来找到相应的段描述符，所以还需要对DS/ES/FS/GS和SS段寄存器进行初始化，使它们都指向位于0x10处的段描述符（即gdt中的数据段描述符）。
  movw \$PROT\_MODE\_DSEG, \%ax \# Our data segment selector movw \%ax,
  \%ds \# -\textgreater{} DS: Data Segment movw \%ax, \%es \#
  -\textgreater{} ES: Extra Segment movw \%ax, \%fs \# -\textgreater{}
  FS movw \%ax, \%gs \# -\textgreater{} GS movw \%ax, \%ss \#
  -\textgreater{} SS: Stack Segment

  在保护模式下，所有的内存寻址将经过分段机制的存储管理来完成，即每个虚拟地址访问将经过分段机制转换成线性地址，由于这时还没有启动分页模式，所以线性地址就是物理地址。
\end{enumerate}
